package com.liu.concurrent.thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

/**
方式二：
设置 1 个临界区以实现 1 个哲学家 “同时”拿起左右  2 把叉子的效果。
即进入临界区之后，保证成功获取到左右 2 把叉子 并 执行相关代码后，才退出临界区。
评论区看到有题友说方法2就是“只让1个哲学家就餐”的思路，无需将叉子视为ReentrantLock。
下面我也给出了“只允许1个哲学家就餐”的代码。
但是2者之间还是有细微的差别：
方法2是在成功拿起左右叉子之后就退出临界区，而“只让1个哲学家就餐”是在拿起左右叉子 + 吃意面 + 放下左右叉子 一套流程走完之后才退出临界区。
前者的情况可大概分为2种，举具体例子说明(可参照上面给出的图片)：

1号哲学家拿起左右叉子(1号叉子 + 2号叉子)后就退出临界区，此时4号哲学家成功挤进临界区，他也成功拿起了左右叉子(0号叉子和4号叉子)，然后就退出临界区。
1号哲学家拿起左右叉子(1号叉子 + 2号叉子)后就退出临界区，此时2号哲学家成功挤进临界区，他需要拿起2号叉子和3号叉子，但2号叉子有一定的概率还被1号哲学家持有(1号哲学家意面还没吃完)，因此2号哲学家进入临界区后还需要等待2号叉子。至于3号叉子，根本没其他人跟2号哲学家争夺，因此可以将该种情况视为“2号哲学家只拿起了1只叉子，在等待另1只叉子”的情况。

总之，第1种情况即先后进入临界区的2位哲学家的左右叉子不存在竞争情况，因此先后进入临界区的2位哲学家进入临界区后都不用等待叉子，直接就餐。此时可视为2个哲学家在同时就餐(当然前1个哲学家有可能已经吃完了，但姑且当作是2个人同时就餐)。
第2种情况即先后进入临界区的2位哲学家的左右叉子存在竞争情况(说明这2位哲学家的编号相邻)，因此后进入临界区的哲学家还需要等待1只叉子，才能就餐。此时可视为只有1个哲学家在就餐。
至于“只允许1个哲学家就餐”的代码，很好理解，每次严格地只让1个哲学家就餐，由于过于严格，以至于都不需要将叉子视为ReentrantLock。
方法2有一定的概率是“并行”，“只允许1个哲学家就餐”是严格的“串行”。
 */
public class DiningPhilosophers3 {
	public DiningPhilosophers3() {

	}

	/**
	 *
	 * @param philosopher 哲学家的编号
	 * @param pickLeftFork 拿起左边的叉子
	 * @param pickRightFork 拿起右边的叉子
	 * @param eat 吃东西
	 * @param putLeftFork 放下左边的叉子
	 * @param putRightFork 放下右边的叉子
	 * @throws InterruptedException
	 */
	public void wantsToEat(int philosopher, Runnable pickLeftFork, Runnable pickRightFork, Runnable eat,
			Runnable putLeftFork, Runnable putRightFork) throws InterruptedException {
		int leftFork = (philosopher + 1) % 5;	//左边的叉子 的编号
		int rightFork = philosopher;	//右边的叉子 的编号

		eatLimit.acquire();	//限制的人数 -1

		lockList[leftFork].lock();	//拿起左边的叉子
		lockList[rightFork].lock();	//拿起右边的叉子

		pickLeftFork.run();	//拿起左边的叉子 的具体执行
		pickRightFork.run();	//拿起右边的叉子 的具体执行

		eat.run();	//吃意大利面 的具体执行

		putLeftFork.run();	//放下左边的叉子 的具体执行
		putRightFork.run();	//放下右边的叉子 的具体执行

		lockList[leftFork].unlock();	//放下左边的叉子
		lockList[rightFork].unlock();	//放下右边的叉子

		eatLimit.release();//限制的人数 +1
	}

	// 1个Fork视为1个ReentrantLock，5个叉子即5个ReentrantLock，将其都放入数组中
	private ReentrantLock[] lockList = { new ReentrantLock(), new ReentrantLock(), new ReentrantLock(),
			new ReentrantLock(), new ReentrantLock() };

	// 限制 最多只有4个哲学家去持有叉子
	private Semaphore eatLimit = new Semaphore(4);
}
